﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

namespace EnterpriseLibraryExtensions.Unity.ServiceModel
{
    /// <summary>
    /// The UnityServiceBehavior class can be added to a ServiceHost's behavior collection.
    /// Doing this, will make Unity resolve service instance and thereby enable dependency injection for services.
    /// </summary>
    public class UnityServiceBehavior : BehaviorExtensionElement, IServiceBehavior
    {
        /// <summary>
        /// The serviceHostBase parameter name.
        /// </summary>
        public const string ServiceHostBaseParameter = "serviceHostBase";

        /// <summary>
        /// The attribute used for specifying the Unity container to use.
        /// </summary>
        public const string ContainerNameProperty = "containerName";

        /// <summary>
        /// The attribute used to specify if the global container should be used.
        /// </summary>
        public const string UseGlobalContainerProperty = "UseGlobalContainer";

        /// <summary>
        /// The name of the channelDispatcher parameter used by this class.
        /// </summary>
        public const string ChannelDispatcherParameter = "channelDispatcher";

        /// <summary>
        /// The properties supported by the UnityServiceBehavior extension element.
        /// </summary>
        private ConfigurationPropertyCollection _properties;

        /// <summary>
        /// Initializes a new instance of the UnityServiceBehavior class and makes it use the default Unity container.
        /// </summary>
        public UnityServiceBehavior()
            : this(string.Empty)
        {
        }

        /// <summary>
        /// Initializes a new instance of the UnityServiceBehavior class.
        /// </summary>
        /// <param name="containerName">The name of the Unity container to use (from the configuration file). Can be null or empty to use default container.</param>
        public UnityServiceBehavior(string containerName)
        {
            this.ContainerName = containerName;
        }

        /// <summary>
        /// Initializes a new instance of the UnityServiceBehavior class and makes it possible to specify whether or not to use
        /// the global container specified on <see cref="EnterpriseLibraryContainer.Current"/>. Specifying false will initialize services
        /// using the default container from the configuration (same as calling the constructor with an empty container name).
        /// </summary>
        /// <param name="useGlobalContainer">Pass true to make use of the global provider.</param>
        public UnityServiceBehavior(bool useGlobalContainer)
        {
            this.UseGlobalContainer = useGlobalContainer;
        }

        /// <summary>
        /// Gets or sets a value indicating whether to use the global container or not. The global container must
        /// be initialized and set on the <see cref="EnterpriseLibraryContainer.Current"/> class.
        /// </summary>
        /// <value><c>true</c> if [use global container]; otherwise, <c>false</c>.</value>
        public bool UseGlobalContainer
        {
            get
            {
                return (bool)base[UseGlobalContainerProperty];
            }
            set
            {
                base[UseGlobalContainerProperty] = value;
            }
        }

        /// <summary>
        /// Gets or sets the name of the Unity container to use from the configuration file.
        /// </summary>
        /// <value>The name of the container.</value>
        public string ContainerName
        {
            get
            {
                return (string)base[ContainerNameProperty];
            }
            set
            {
                base[ContainerNameProperty] = value;
            }
        }

        /// <summary>
        /// Returns the type exposed by this behavior element.
        /// </summary>
        /// <returns>A <see cref="T:System.Type"/>.</returns>
        public override Type BehaviorType
        {
            get { return typeof(UnityServiceBehavior); }
        }

        /// <summary>
        /// Returns the collection of custom properties supported by the unity service behavior.
        /// </summary>
        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new ConfigurationPropertyCollection();
                    _properties.Add(new ConfigurationProperty(ContainerNameProperty, typeof(string), string.Empty));
                    _properties.Add(new ConfigurationProperty(UseGlobalContainerProperty, typeof(bool), false));
                }

                return _properties;
            }
        }

        /// <summary>
        /// Provides the ability to pass custom data to binding elements to support the contract implementation.
        /// </summary>
        /// <param name="serviceDescription">The service description of the service.</param>
        /// <param name="serviceHostBase">The host of the service.</param>
        /// <param name="endpoints">The service endpoints.</param>
        /// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        /// <summary>
        /// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="serviceHostBase">The host that is currently being built.</param>
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            ApplyDispatchBehavior(serviceHostBase);
        }

        /// <summary>
        /// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="serviceHostBase">The service host that is currently being constructed.</param>
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        /// <summary>
        /// Applies the UnityInstanceProvider dispatch behavior to all endpoints in the host provided.
        /// </summary>
        /// <param name="serviceHostBase">A valid serviceHostBase (not null).</param>
        internal void ApplyDispatchBehavior(ServiceHostBase serviceHostBase)
        {
            if (serviceHostBase == null)
            {
                throw new ArgumentNullException(ServiceHostBaseParameter);
            }

            foreach (ChannelDispatcher channelDispatcher in GetChannelDispatchers(serviceHostBase))
            {
                foreach (var endPoint in GetEndpoints(channelDispatcher))
                {
                    if (this.UseGlobalContainer)
                    {
                        endPoint.DispatchRuntime.InstanceProvider = CreateInstanceProviderWithGlobalContainer();
                    }
                    else
                    {
                        endPoint.DispatchRuntime.InstanceProvider = CreateInstanceProviderWithNamedContainer();
                    }
                }
            }
        }

        /// <summary>
        /// Returns the provided serviceHostBase's channel dispatchers.
        /// Method exists to be overridable in a test.
        /// </summary>
        /// <param name="serviceHostBase">A valid serviceHostBase instance.</param>
        /// <returns>The channel dispachers from the serviceHostBase.</returns>
        protected virtual IEnumerable<ChannelDispatcherBase> GetChannelDispatchers(ServiceHostBase serviceHostBase)
        {
            if (serviceHostBase == null)
            {
                throw new ArgumentNullException(ServiceHostBaseParameter);
            }

            return serviceHostBase.ChannelDispatchers;
        }

        /// <summary>
        /// Returns the provided ChannelDispatcher's endpoints. This methods makes unit testing
        /// easier by providing an overridable method.
        /// </summary>
        /// <param name="channelDispatcher">A valid channelDispatcher.</param>
        /// <returns>An IEnumerable of EndpointDispatchers</returns>
        protected virtual IEnumerable<EndpointDispatcher> GetEndpoints(ChannelDispatcher channelDispatcher)
        {
            if (channelDispatcher == null)
            {
                throw new ArgumentNullException(ChannelDispatcherParameter);
            }

            return channelDispatcher.Endpoints;
        }

        /// <summary>
        /// The method will return a new <see cref="UnityInstanceProvider"/> class which is based on the
        /// global container registered on <see cref="EnterpriseLibraryContainer.Current"/>.
        /// </summary>
        /// <returns>A UnityInstanceProvider instance.</returns>
        protected virtual IInstanceProvider CreateInstanceProviderWithNamedContainer()
        {
            return new UnityInstanceProvider(this.ContainerName);
        }

        /// <summary>
        /// Create a new <see cref="UnityInstanceProvider"/> class based on a named Unity container.
        /// </summary>
        /// <returns>A UnityInstanceProvider instance.</returns>
        protected virtual IInstanceProvider CreateInstanceProviderWithGlobalContainer()
        {
            return new UnityInstanceProvider(this.UseGlobalContainer);
        }

        /// <summary>
        /// Creates and returns a new instance of the <see cref="EnterpriseLibraryExtensions.Unity.ServiceModel.UnityServiceBehavior"/> class.
        /// </summary>
        /// <returns>A new instance.</returns>
        protected override object CreateBehavior()
        {
            if (UseGlobalContainer)
            {
                return new UnityServiceBehavior(true);
            }

            return new UnityServiceBehavior(this.ContainerName);
        }
    }
}
